<template>
  <div class="interaction_container">
    <!-- 头部内容 -->
    <div class="header">
      <router-link class="returned" to="/home"><i class="iconfont backarrow"></i></router-link>
      <div class="weather_box">
        <div id="he-plugin-simple"></div>
      </div>
      <router-link class="logo" to="/home"><i class="iconfont wandaicon"></i></router-link>
    </div>
    <!-- "语音采集按钮" -->
    <div class="voice_container">
      <router-link to="#"><i class="iconfont yuyin" @click="startRecorder"></i></router-link>
    </div>
    <!-- 主体内容 -->
    <div class="interaction_box">
      <!-- 标题内容 -->
      <div class="title"><i class="iconfont robot"></i>智能互动</div>
      <!-- 人机智能互动内容 -->
      <div class="dialogue_container">
        <template v-for="chat in chats">
          <!-- “机器人”问答 -->
          <div class="robot_box" :key="chat.index" v-if="chat.position == 'left'">
            <!-- "头像" -->
            <div class="robot_head">
              <img src="../Interaction/images/robothead.svg" alt="" srcset="" />
            </div>
            <!-- "对话内容" -->
            <div class="robot_content">{{ chat.content }}</div>
          </div>
          <!-- "客户"问答 -->
          <div class="user_box" :key="chat.index" v-if="chat.position == 'right'">
            <!-- "对话内容" -->
            <div class="user_content">{{ chat.text }}</div>
            <!-- "头像" -->
            <div class="user_head"><i class="iconfont userhead"></i></div>
          </div>
        </template>
      </div>
    </div>
    <div class="audio-layer" v-if="showModal"></div>
    <div v-if="showModal" class="mask">
      <div class="model-center">
        <!-- <div class="audio-cancel">
          <span @click="cancelRecoding">X</span>
        </div>
        <div class="audio-to-text">
          <span @click="sendAudio">文</span>
        </div> -->
        <div class="wave_line">
          <WaveLine
            v-show="recorderWaveVisible"
            ref="recorderWave"
            :recorder-wave-visible="recorderWaveVisible"
            @emitRecorderWaveVisible="emitRecorderWaveVisible"
          />
        </div>
      </div>
    </div>

    <audio controls id="audio" autoplay>
      <source src="" type="audio/mpeg" />
      您的浏览器不支持 audio 元素。
    </audio>
  </div>
</template>

<script>
import '@/libs/Recorder/recorder-core.js';
import '@/libs/Recorder/wav.js';
import { isEmpty, parseTime, uuid } from '@/utils/tools';
import { getWsLocation } from '@/utils/common';
import { ask, tts } from '@/api/robot';
import WaveLine from '@/components/WaveLine';

export default {
  name: 'Interaction',
  components: {
    WaveLine,
  },
  data() {
    return {
      chats: [],
      isRecording: false,
      recognizedText: '',
      isSend: false,
      voiceInputTime: 0,
      websocket: null,
      recorder: null,
      isPreparedEnv: false,
      userId: uuid(),
      platform: 'web',
      audio: undefined,
      showModal: false,
      recorderWaveVisible: false,
    };
  },
  filters: {
    replaceTags(text) {
      return text.replace(/<.*?>/g, '');
    },
  },
  mounted() {
    this.preparedEnv();
    this.audio = document.getElementById('audio');
  },
  methods: {
    cancelRecoding() {
      this.userStopRecorder();
    },
    sendAudio() {
      this.userStopRecorder();
    },
    ask(question) {
      let params = {
        userId: this.userId,
        platform: this.platform,
        question: question,
      };
      ask(params).then((res) => {
        console.log(res);
        this.chats.push({
          position: 'left',
          content: res.data.content,
          text: res.data.text,
        });
        this.autoScrollTop();
        this.playVoice(res.data.tts);
      });
    },
    onSentenceEnd(data) {
      let chat = this.chats.find((chat) => {
        if (chat.requestId === data.requestId) {
          return true;
        }
      });
      console.log(chat);
      if (chat == undefined) {
        chat = {
          position: 'right',
          text: data.msg,
          requestId: data.requestId,
          createTime: parseTime(new Date()),
          msgId: uuid(),
        };
        this.chats.push(chat);
        this.autoScrollTop();
      } else {
        chat.text = data.msg;
      }

      this.ask(data.msg);
    },
    onTranscriptionResultChange(data) {
      console.log(data);
    },
    receiveMsg(msg) {
      console.log('data==>' + msg);
      const res = JSON.parse(msg);
      const event = res.event;
      const data = res.data == undefined ? {} : res.data;
      const message = res.message;
      this.receiveData = data;
      if (event === 'OnRecognitionCompleted') {
        this.requestId = data.requestId;
        this.stopRecorder();
        if (isEmpty(data.recognizedText)) {
          self.$message.warning('未识别出您说的话');
          return;
        } else {
          this.recognizedText = data.recognizedText;
          this.autoScrollTop();
        }
      } else if (event === 'onRecognitionResultChanged') {
        if (isEmpty(this.recognizedText)) {
          this.chats.push({
            position: 'right',
            text: data.recognizedText,
            requestId: data.taskId,
            rules: [],
            createTime: parseTime(new Date()),
          });
        } else {
          this.chats[this.chats.length - 1].text = data.recognizedText;
        }
        this.recognizedText = data.recognizedText;
        this.autoScrollTop();
      } else if (event === 'onSentenceEnd') {
        // 断句结束
        this.stopRecorder();
        if (isEmpty(data.msg)) {
          self.$message.warning('未识别出您说的话');
          return;
        } else {
          this.recognizedText = data.msg;
          this.onSentenceEnd(data);
        }
      } else if (event === 'onTranscriptionResultChange') {
        //返回中间结果
        this.requestId = data.requestId;
        this.recognizedText = data.msg;
        this.onTranscriptionResultChange(data);
      } else if (event === 'end') {
        this.stopRecorder();
      } else if (event === 'asr_already') {
        this.recorder.start();
      } else if (event === 'error' || event === 'fail') {
        this.$message.warning(message);
        this.stopRecorder();
      } else if (event === 'silentSpeech') {
        this.$message.warning('静默时间太长');
        this.stopRecorder();
      }
    },
    /**
     * 开始录音
     */
    startRecorder() {
      if (this.isRecording) {
        // this.stopRecorder();
        return;
      }

      if (this.recorder) {
        this.showModal = true;
        this.stopVoice();
        this.sendDatatToServer('startASR');
        this.recorderWaveVisible = true;
        this.isRecording = true;
      }
    },
    stopRecorder() {
      this.showModal = false;
      this.voiceInputTime = '00:00';
      if (this.recorder) {
        this.isRecording = false;
        this.sendDatatToServer('stopRecord');
        let that = this;
        this.recorder.stop(
          function (blob, duration) {
            console.log(
              blob,
              (window.URL || webkitURL).createObjectURL(blob),
              '时长:' + duration + 'ms'
            );
            if (that.requestId) {
              // that.formData.append('files', blob, that.requestId + '.wav');
            }
          },
          function (msg) {
            console.log('录音失败:' + msg);
          }
        );
      }
      // this.recorderWaveVisible = false;
    },
    playVoice(content, callback) {
      return new Promise((resolve) => {
        if (content) {
          console.log(content);
          tts({
            text: content,
          }).then((data) => {
            let blod = new Blob([data.data], { type: 'audio/wav' });
            let audioSrc = URL.createObjectURL(blod);
            this.audio.src = audioSrc;
            console.log(audioSrc);
            setTimeout(() => {
              this.audio.play();
            }, 1000);

            var event = () => {
              this.audio.removeEventListener('ended', event, true);
              console.log('语音播放结束', this.audio.src);
              URL.revokeObjectURL(this.audio.src);
              callback && callback();
              resolve();
            };
            this.audio.addEventListener('ended', event, true);
            this.audio.addEventListener(
              'error',
              function (error) {
                this.audio.removeEventListener('ended', event, true);
                URL.revokeObjectURL(this.audio.src);
                console.error('语音文件加载失败', error, this.audio.src);
                callback && callback();
                resolve();
              },
              true
            );
          });
        } else {
          callback && callback();
          resolve();
          // reject();
        }
      });
    },
    stopVoice() {
      this.audio.pause();
    },
    preparedEnv() {
      const self = this;
      return new Promise((resolve, reject) => {
        self
          .initWebSocket()
          .then(() => {
            self
              .initRecorder()
              .then(() => {
                // self.$message.success("训练环境加载成功");
                self.isPreparedEnv = true;
                resolve();
              })
              .catch((err) => {
                self.$message.error('麦克风环境加载失败:' + err);
                reject(err);
              });
          })
          .catch((err) => {
            self.$message.error('websocket环境加载失败:' + err);
            reject(err);
          });
      });
    },
    /**
     * 初始化webSocket
     */
    initWebSocket() {
      const self = this;
      return new Promise((resolve, reject) => {
        if ('WebSocket' in window) {
          self.websocket = new WebSocket(getWsLocation('/ws/native-ws'));
        } else {
          reject('该浏览器不支持websocket');
        }

        self.websocket.onopen = function (event) {
          resolve(event);
        };

        self.websocket.onclose = function () {
          console.log('websocket连接已关闭');
          reject('websocket连接已关闭');
        };

        self.websocket.onmessage = function (event) {
          self.receiveMsg(event.data);
        };

        self.websocket.onerror = function (event) {
          reject('websocket连接失败:' + event);
        };
      });
    },

    // 往websocket服务器发送数据
    sendDatatToServer(event, message) {
      var event_ = {
        event: event,
        message: message,
      };
      if (this.websocket.readyState === 1) {
        this.websocket.send(JSON.stringify(event_));
      }
    },
    /**
     * 初始化麦克风，询问权限
     */
    initRecorder() {
      const self = this;
      if (self.recorder) {
        self.closeRecorder();
      }
      return new Promise((resolve, reject) => {
        self.recorder = window.Recorder({
          type: 'wav',
          bitRate: 16,
          sampleRate: 16000,
          // bufferSize: 4096,
          onProcess: function (buffers, level, time, sampleRate, isPush) {
            self.receiveVoiceData(buffers, level, time, sampleRate, isPush);
          },
          takeoffEncodeChunk: function (chunkBytes) {
            console.log(chunkBytes);
          },
        });
        this.recorder.open(
          function () {
            resolve();
          },
          function (e, isUserNotAllow) {
            reject('麦克风打开失败:' + e);
            console.log(isUserNotAllow);
          }
        );
      });
    },
    /**
     * 关闭麦克风
     */
    closeRecorder() {
      if (this.recorder) {
        this.recorder.close(function () {});
      }
    },
    // 关闭websocket
    closeWebSocket() {
      if (this.websocket) {
        this.websocket.close();
      }
    },

    // 用户手动停止录音
    userStopRecorder() {
      this.stopRecorder();
      if (!isEmpty(this.receiveData.msg)) {
        this.recognizedText = this.receiveData.msg;
        this.onSentenceEnd(this.receiveData);
      } else {
        this.isRecording = false;
      }
    },
    // 往服务器上发送二进制数据
    receiveVoiceData(buffers, level, time, sampleRate, isPush) {
      var self = this;
      if (isPush) {
        if (self.isPreparedEnv && self.websocket.readyState === 1) {
          self.websocket.send(
            new Blob(buffers, {
              type: 'audio/pcm',
            })
          );
        }
      } else {
        // waveline 绘制方法调用
        if (self.$refs.recorderWave) {
          self.$refs.recorderWave.receiveVoiceData(buffers, level, time, sampleRate);
        }
      }
    },
    emitRecorderWaveVisible() {
      this.recorderWaveVisible = false;
      this.isCollapseing = true;
      setTimeout(() => {
        this.recorderWaveVisible = true;
        this.isCollapseing = false;
      }, 300);
    },
    autoScrollTop: function () {
      // 消息中，有些包含图片，如果加载不完，则高度计算会有误
      window.setTimeout(function () {
        let htmlDiv = document.getElementsByClassName('dialogue_container')[0];
        htmlDiv.scrollTop = htmlDiv.scrollHeight;
      }, 200);
    },
  },
  beforeDestroy() {
    this.closeRecorder();
    this.closeWebSocket();
    window.clearInterval(this.totalTrainingTimer);
  },
  created() {
    window.WIDGET = {
      CONFIG: {
        modules: '01234',
        background: '5',
        tmpColor: '404A56',
        tmpSize: '40',
        cityColor: '404A56',
        citySize: '0',
        aqiColor: '404A56',
        aqiSize: '40',
        weatherIconSize: '60',
        alertIconSize: '60',
        padding: '10px 10px 10px 10px',
        shadow: '0',
        language: 'auto',
        fixed: 'false',
        vertical: 'top',
        horizontal: 'left',
        key: 'c96c78edf3474b1585e0067b5b5b8626',
      },
    };
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = 'https://widget.qweather.net/simple/static/js/he-simple-common.js?v=2.0';
    document.getElementsByTagName('head')[0].appendChild(script);
  },
};
</script>

<style lang="less" scoped>
#audio {
  display: none;
}
.interaction_container {
  width: 1080px;
  height: 1920px;
  position: relative;
  background-color: rgba(248, 248, 248, 0.5);
  .voice_container {
    width: 220px;
    height: 180px;
    background-color: #cfe5ff;
    position: absolute;
    top: 25%;
    right: 0;
    border-top-left-radius: 90px;
    border-bottom-left-radius: 90px;
    overflow: hidden;
    display: flex;
    justify-content: flex-start;
    align-items: center;
    transition: 0.5s;
    z-index: 1000;
    & > a {
      width: 180px;
      height: 180px;
      display: flex;
      justify-content: center;
      align-items: center;
      background-color: #337afc;
      border: 10px solid #80b7f9;
      border-radius: 90px;
      overflow: hidden;
      color: #fff;
      transition: 0.5s;
      cursor: pointer;

      & > .iconfont {
        transition: 0.3s;
        font-size: 5.2rem;
        opacity: 1;
      }

      &:hover,
      &:active {
        transition: 0.3s;
        background-color: #1763ef;
        border: 16px solid #80b7f9;
        box-shadow: inset 0 0 20px rgba(11, 56, 130, 0.5);
        & > .iconfont {
          transition: 0.3s;
          font-size: 4.6rem;
          opacity: 0.8;
        }
      }
    }
    &:hover,
    &:active,
    &:focus {
      transition: 0.5s;
      width: 240px;
      background-color: #bcdaff;
    }
  }

  .header {
    width: 100%;
    height: 220px;
    margin-bottom: 42px;
    box-sizing: border-box;
    position: relative;
    .weather_box {
      width: auto;
      height: 98px;
      font-weight: bold;
      position: absolute;
      top: 50px;
      left: 220px;
      display: flex;
      justify-content: left;
      align-items: center;
    }
    .returned {
      width: 366px;
      height: 366px;
      color: #fff;
      border-radius: 180px;
      overflow: hidden;
      position: absolute;
      top: 0;
      left: 0;
      transition: 0.5s;
      transform: translate(-40%, -40%);
      background-color: #ff4d60;
      background-image: -o-linear-gradient(-45deg, #ff4d60, #ffda4b);
      background-image: -moz-linear-gradient(-45deg, #ff4d60, #ffda4b);
      background-image: -webkit-linear-gradient(-45deg, #ff4d60, #ffda4b);
      background-image: linear-gradient(-45deg, #ff4d60, #ffda4b);
      &:hover,
      &:active {
        transition: 0.5s;
        transform: translate(-35%, -35%);
        background-image: -o-linear-gradient(-45deg, #ff344a, #ffea2f);
        background-image: -moz-linear-gradient(-45deg, #ff344a, #ffea2f);
        background-image: -webkit-linear-gradient(-45deg, #ff344a, #ffea2f);
        background-image: linear-gradient(-45deg, #ff344a, #ffea2f);
        & > .iconfont {
          transition: 0.5s;
          font-size: 5.5rem;
        }
      }
      & > .iconfont {
        transition: 0.5s;
        font-size: 5rem;
        position: absolute;
        left: 52%;
        top: 55%;
      }
    }
    .logo {
      width: 108px;
      height: 108px;
      display: flex;
      justify-content: center;
      align-items: center;
      color: #e30016;
      float: right;
      margin: 42px 60px 0 0;
      & > .iconfont {
        font-size: 6rem;
      }
    }
  }
  .interaction_box {
    width: 100%;
    padding: 30px 40px;
    .title {
      width: auto;
      height: 116px;
      margin: 0 10px;
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 2.8rem;
      color: #404a56;
      font-weight: bold;
      background-color: #fff;
      border-radius: 60px;
      overflow: hidden;
      box-shadow: 0 0 40px rgba(92, 97, 219, 0.2);

      & > .iconfont {
        font-size: 4.4rem;
        background-image: -o-linear-gradient(to top, #ff344a, #ffea2f);
        background-image: -moz-linear-gradient(to top, #ff344a, #ffea2f);
        background-image: -webkit-linear-gradient(to top, #ff344a, #ffea2f);
        background-image: linear-gradient(to top, #ff344a, #ffea2f);
        -webkit-background-clip: text;
        background-clip: text;
        -webkit-text-fill-color: transparent;
        color: transparent;
        margin-right: 24px;
        font-weight: initial;
      }
    }
    .dialogue_container {
      width: 100%;
      max-height: 1430px;
      overflow-x: hidden;
      overflow-y: auto;
      margin-top: 60px;
      box-sizing: border-box;
      position: relative;
      //高宽分别对应横竖滚动条的尺寸
      &::-webkit-scrollbar {
        width: 10px;
        height: 0;
      }
      //内阴影 和 圆角
      &::-webkit-scrollbar-track {
        box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1);
        border-radius: 10px;
      }
      // 内阴影 圆角
      &::-webkit-scrollbar-thumb {
        border-radius: 10px;
        box-shadow: inset 0 0 6px rgba(93, 101, 131, 0.1);
        background-color: #ebebeb;
      }

      .robot_box,
      .user_box {
        width: 100%;
        margin-bottom: 40px;
        display: flex;
        .robot_head,
        .user_head {
          width: 94px;
          height: 94px;
          border-radius: 47px;
          overflow: hidden;
          display: flex;
        }
        .robot_content,
        .user_content {
          width: auto;
          max-width: 69%;
          min-height: 94px;
          line-height: 46px;
          padding: 20px 30px;
          text-align: justify;
          font-size: 1.6rem;
          border-radius: 47px;
          word-wrap: break-word;
          word-break: break-all;
          & > img {
            width: auto;
            max-width: 100%;
            border: 6px solid rgba(255, 255, 255, 0.7);
            border-radius: 32px;
            overflow: hidden;
            margin: 15px 0;
          }
        }
      }

      .robot_box {
        padding: 20px 0 20px 20px;
        justify-content: flex-start;
        align-items: flex-end;
        .robot_head {
          justify-content: center;
          align-items: center;
          background-color: #fff;
          box-shadow: 0 0 15px rgba(0, 112, 224, 0.2);
          & > img {
            width: 74%;
            height: auto;
          }
        }
        .robot_content {
          color: #fff;
          background-color: #337afc;
          border-bottom-left-radius: 0;
          margin-left: 20px;
          box-shadow: 0 0 10px rgba(11, 56, 130, 0.5);
        }
      }

      .user_box {
        padding: 20px 20px 20px 0;
        justify-content: flex-end;
        align-items: flex-end;
        .user_head {
          justify-content: center;
          align-items: center;
          color: #fff;
          background-color: #7558ff;
          background-image: linear-gradient(to top, #7558ff, #4593ff);
          background-image: -o-linear-gradient(to top, #7558ff, #4593ff);
          background-image: -moz-linear-gradient(to top, #7558ff, #4593ff);
          background-image: -webkit-linear-gradient(to top, #7558ff, #4593ff);
          box-shadow: 0 0 15px rgba(93, 101, 131, 0.5);
          & > .iconfont {
            font-size: 3.6rem;
          }
        }
        .user_content {
          color: #404a56;
          background-color: #ecf1fc;
          border-bottom-right-radius: 0;
          margin-right: 20px;
          box-shadow: 0 0 10px rgba(11, 56, 130, 0.2);
        }
      }
    }
  }

  .audio-layer {
    background-color: #000000;
    width: 160%;
    height: 50%;
    opacity: 0.1;
    z-index: 1;
    bottom: 0px;
    position: absolute;
    border-radius: 800px 800px 0 0;
    left: 50%;
    transform: translate(-50%, 0%);
  }

  .mask {
    background: rgba(0, 0, 0, 0.3);
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 1;
  }
}

.model-center {
  display: flex;
  position: absolute;
  top: 28%;
  margin-left: 13%;
  width: 100%;
  .audio-cancel {
    height: 100px;
    width: 100px;
    border-radius: 100px;
    background-color: black;
    z-index: 10000;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .audio-to-text {
    height: 100px;
    width: 100px;
    border-radius: 100px;
    background-color: black;
    z-index: 10000;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  span {
    font-size: 20px;
    color: white;
  }
}
</style>
